﻿namespace CloundNinja.Metering.RowCountsProvider
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    using CloudNinja.Metering.Common;
    using CloudNinja.Metering.Common.Configuration;
    using CloudNinja.Metering.Common.Logging;
    using CloudNinja.Metering.Data;
    using CloudNinja.Metering.Data.Models;
    using CloudNinja.Metering.Providers;

    using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;

    [ProviderConfigElementType("CloundNinja.Metering.RowCountsProvider.Configuration.TenantRowCountProviderSettingsElement, CloundNinja.Metering.RowCountsProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")]
    public class RowCountsProvider : MeteringProviderBase
    {
        private readonly IEnumerable<TenantRowCountSpec> tenantRowCountsSources;

        public RowCountsProvider(TimeSpan collectInterval, IMeteringRepository repository, ILogger logger, IMeteringContext meteringContext, IEnumerable<TenantRowCountSpec> tenantRowCountsSources)
            : base(collectInterval, repository, logger, meteringContext)
        {
            this.tenantRowCountsSources = tenantRowCountsSources;
        }

        public override string Name
        {
            get
            {
                return "RowCountsProvider";
            }
        }

        public override void Initialize()
        {
            this.Logger.LogInformation("Initialized RowCountsProvider Provider");
        }

        public override void Collect()
        {
            var results = new List<TenantRowCount>();
            var lockObject = new object();

            this.StartTiming();
            using (var sem = new SemaphoreSlim(1))
            {
                Parallel.ForEach(
                    this.tenantRowCountsSources,
                    () => new List<TenantRowCount>(),
                    (tenantRowCountSource, loopstate, localList) =>
                    {
                        try
                        {
                            localList.AddRange(this.GetTenantDistribution(tenantRowCountSource));
                        }
                        catch (Exception exception)
                        {
                            this.Logger.LogException(exception);
                        }
                        return localList;
                    },
                    finalResult =>
                    {
                        lock (lockObject)
                        {
                            results.AddRange(finalResult);
                        }
                    });
            }

            var groupedResult = from result in results
                                group result by result.TenantId
                                    into tenantGroup
                                    select
                                        new MeterData
                                        {
                                            MeterTypeId = "RowCounts",
                                            TenantId = tenantGroup.Key,
                                            Count = 0,
                                            Value = tenantGroup.Sum(t => t.Count),
                                            TimePeriod = DateTimeOffset.Now.DateTime
                                        };

            this.MeteringRepository.Write(groupedResult);

            this.StopTiming();
        }

        public override void Stop()
        {
            this.Logger.LogInformation("Stopping RowCountsProvider Provider");
        }

        private IEnumerable<TenantRowCount> GetTenantDistribution(TenantRowCountSpec tenantRowCountSource)
        {
            var result = new List<TenantRowCount>();
            using (var connection = new ReliableSqlConnection(tenantRowCountSource.ConnectionString, this.MeteringContext.SqlAzureConnectionRetryPolicy, this.MeteringContext.SqlAzureCommandRetryPolicy))
            {
                connection.Open();
                var command = connection.CreateCommand();
                command.CommandText = string.Format(
                    "SELECT {0}, count({1}) AS TENANTROWCOUNT FROM {2} GROUP BY {0}",
                    tenantRowCountSource.TenantIdColumn,
                    tenantRowCountSource.CountColumn,
                    tenantRowCountSource.TableName);

                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var tenantIdValue = reader[tenantRowCountSource.TenantIdColumn] as string;
                        if (string.IsNullOrEmpty(tenantIdValue))
                        {
                            continue;
                        }

                        tenantIdValue = tenantIdValue.Trim();

                        var tenantId = Helper.GetTenantId(tenantIdValue, tenantRowCountSource.TenantIdPattern);
                        if (!tenantId.Equals(string.Empty))
                        {
                            result.Add(
                                new TenantRowCount
                                {
                                    TenantId = tenantId,
                                    Count = Convert.ToInt64(reader["TENANTROWCOUNT"])
                                });
                        }
                    }
                }
            }

            return result;
        }
    }
}
